package main

import (
	"crypto/sha256"
	"fmt"
	"net/http"
	"strconv"
	"sync"
	"time"

	"fabric-sdk/mysql"
	"fabric-sdk/sdk"
	"fabric-sdk/utils"

	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
)

type Server struct {
	SdkChannelInfos []sdk.SdkChannelInfo
	db              *mysql.Database
}

type Object struct {
	ID   string
	Hash string
}

type KafkaConsumer struct {
	Node  string
	Topic string
}

const channelNum = 2 // channel num

// var id_ch = make(chan string, 200)
var hash_ch = make(chan Object, 200000)
var entry_ch = make(chan mysql.Entry, 10000)

var wg sync.WaitGroup

func (s *Server) queryAHash(id string) {
	defer wg.Done()
	// timeStart := time.Now().UnixNano()
	// channelIdToPut := utils.StrHashToNum(id, 2)
	channelIdToPut := 0
	_, err := s.SdkChannelInfos[channelIdToPut].Client.Query(channel.Request{ChaincodeID: s.SdkChannelInfos[channelIdToPut].ChaincodeID, Fcn: "read", Args: [][]byte{[]byte(id)}})
	if err != nil {
		fmt.Println("<-myerror-> ", err)
	}
	// else {
	// 	fmt.Printf("<-查询对象ID为: '%s' 的对象哈希: '%s'-> 花费时间: %fms\n", id, string(response.Payload), ((float64)(time.Now().UnixNano()-timeStart))/1e6)
	// }
}

func (s *Server) query_hash(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	timeStart := time.Now().UnixNano()
	id := r.URL.Query().Get("ID")
	channelIdToPut := utils.StrHashToNum(id, 2)
	// channelIdToPut := 0
	response, err := s.SdkChannelInfos[channelIdToPut].Client.Query(channel.Request{ChaincodeID: s.SdkChannelInfos[channelIdToPut].ChaincodeID, Fcn: "read", Args: [][]byte{[]byte(id)}})
	if err != nil {
		fmt.Println("<-myerror-> ", err)
	} else {
		fmt.Printf("<-查询对象ID为: '%s' 的对象哈希: '%s'-> 花费时间: %fms\n", id, string(response.Payload), ((float64)(time.Now().UnixNano()-timeStart))/1e6)
	}

	_, _ = w.Write([]byte(string(response.Payload)))
	// fmt.Printf("<-put obj_id: '%s' to channel-> cost time: %fms\n", id, ((float64)(time.Now().UnixNano()-timeStart))/1e6)
}

func (s *Server) putHashToFabric() {
	defer wg.Done()
	for obj_put := range hash_ch {
		// timeStart := time.Now().UnixNano()
		channelIdToPut := utils.StrHashToNum(obj_put.ID, channelNum)
		_, err := s.SdkChannelInfos[channelIdToPut].Client.Execute(channel.Request{ChaincodeID: s.SdkChannelInfos[channelIdToPut].ChaincodeID, Fcn: "create", Args: [][]byte{[]byte(obj_put.ID), []byte(obj_put.Hash)}})
		if err != nil {
			fmt.Println(err)
		}
		// else {
		// 	fmt.Printf("<-添加对象ID: '%s', Hash length: %d, 到Fabric-> 花费时间: %fms\n", obj_put.ID, len(obj_put.Hash), ((float64)(time.Now().UnixNano()-timeStart))/1e6)
		// }
	}
}

func (s *Server) putAHashToFabric(obj_put Object) {
	// timeStart := time.Now().UnixNano()
	// channelIdToPut := utils.StrHashToNum(obj_put.ID, channelNum)
	channelIdToPut := 0
	_, err := s.SdkChannelInfos[channelIdToPut].Client.Execute(channel.Request{ChaincodeID: s.SdkChannelInfos[channelIdToPut].ChaincodeID, Fcn: "create", Args: [][]byte{[]byte(obj_put.ID), []byte(obj_put.Hash)}})
	if err != nil {
		fmt.Println(err)
	}
	// else {
	// 	fmt.Printf("<-添加对象ID: '%s', Hash length: %d, 到Fabric-> 花费时间: %fms\n", obj_put.ID, len(obj_put.Hash), ((float64)(time.Now().UnixNano()-timeStart))/1e6)
	// }
}

func (s *Server) putEntryToSQL() {
	defer wg.Done()
	for entry_put := range entry_ch {
		err := s.db.InsertEntry(entry_put, mysql.TableName)
		if err != nil {
			fmt.Println("error InsertEntry %v", err)
		}
	}
}

func ServerStart(s Server, kafkaBroker string, kafkaTopic string, orgID int) {
	// for i := 0; i < cap(hash_ch); i++ {
	// 	obj := Object{
	// 		ID:   "objects_" + strconv.Itoa(i),
	// 		Hash: "ec13a4c02aaa0ef53893742d5bf7d14c960333e0275a9c6472774e85c9f502c5",
	// 	}
	// 	hash_ch <- obj
	// }
	// close(hash_ch)
	// fmt.Println("finish prepare objs")

	s.db = &mysql.Database{}
	err := s.db.InitDB()
	if err != nil {
		fmt.Println("error InitDB %v", err)
		return
	}
	err = s.db.DropTable(mysql.TableName)
	if err != nil {
		fmt.Println("error DropTable %v", err)
		return
	}
	err = s.db.CreateTable(mysql.TableName)
	if err != nil {
		fmt.Println("error CreateTable %v", err)
		return
	}

	// timeStart := time.Now()

	// // wg.Add(2)
	// entry := mysql.Entry{
	// 	Name:     "Zhang San",
	// 	WorkID:   "0000",
	// 	DeviceID: "0001",
	// 	FilePath: "/usr/local/picture/test.jpg",
	// 	FileHash: "ec13a4c02aaa0ef53893742d5bf7d14c960333e0275a9c6",
	// }
	// // go func() {
	// // 	defer wg.Done()
	// err = s.db.InsertEntry(entry, mysql.TableName)
	// if err != nil {
	// 	fmt.Println("error InsertEntry %v", err)
	// }
	// // }()
	// timeStart1 := time.Now()

	// // go func() {
	// // 	defer wg.Done()
	// sqlData := entry.Name + entry.WorkID + entry.DeviceID + entry.FilePath
	// h := sha256.New()
	// h.Write([]byte(sqlData))
	// obj := Object{
	// 	ID:   entry.FileHash,
	// 	Hash: string(h.Sum(nil)),
	// }
	// s.putAHashToFabric(obj)
	// // }()
	// // wg.Wait()

	// timeStart2 := time.Now()

	// time.Sleep(3 * time.Second)

	// queryNum := 100000
	// timeStart3 := time.Now()
	// wg.Add(queryNum)
	// // for i := 0; i < queryNum; i++ {
	// // 	// s.db.SelectEntry(mysql.TableName)
	// // 	go func() {
	// // 		defer wg.Done()
	// // 		s.db.SelectEntry(mysql.TableName)
	// // 	}()
	// // }

	// // timeStart4 := time.Now()
	// for i := 0; i < queryNum; i++ {
	// 	go s.queryAHash(entry.FileHash)
	// }
	// wg.Wait()

	// timeStart5 := time.Now()

	// fmt.Println("sql insert:", timeStart1.Sub(timeStart).Microseconds(), "μs, ", timeStart2.Sub(timeStart1).Microseconds(), "μs")
	// // fmt.Println("sql select:", timeStart4.Sub(timeStart3).Microseconds(), "μs, ", timeStart5.Sub(timeStart4).Microseconds(), "μs")
	// fmt.Println("sql select:", timeStart5.Sub(timeStart3).Microseconds(), "μs")

	timeStart := time.Now().Unix()
	entrys := []mysql.Entry{}
	for i := 0; i < cap(hash_ch); i++ {
		entry := mysql.Entry{
			Name:     "Zhang San " + strconv.Itoa(i),
			WorkID:   strconv.Itoa(i),
			DeviceID: strconv.Itoa(i),
			FilePath: "/usr/local/picture/test.jpg",
			FileHash: "ec13a4c02aaa0ef53893742d5bf7d14c960333e0275a9c6" + strconv.Itoa(i),
		}
		// entry_ch <- entry
		entrys = append(entrys, entry)

		sqlData := entry.Name + entry.WorkID + entry.DeviceID + entry.FilePath
		h := sha256.New()
		h.Write([]byte(sqlData))
		obj := Object{
			ID:   entry.FileHash,
			Hash: string(h.Sum(nil)),
		}
		hash_ch <- obj
	}
	close(hash_ch)
	// close(entry_ch)
	goNum := 1200 * 2
	wg.Add(goNum)

	// go s.putEntryToSQL()
	for i := 0; i < goNum; i++ {
		// go s.query_hash()
		go s.putHashToFabric()
	}
	err = s.db.InsertEntrys(entrys, mysql.TableName)
	if err != nil {
		fmt.Println("error InsertEntry %v", err)
	}
	wg.Wait()

	durationTime := ((float64)(time.Now().UnixNano() - timeStart)) / 1e9
	fmt.Printf("cost time: %fs, TPS: %f\n", durationTime, (float64)(cap(hash_ch))/durationTime)
}
